var VNCClient = Class.create(MojaUtils.Observable,{
	lastDown: [],
	maxRequestRate : 50,
	curRequests : 0,
	Messages: {
		keyDowns : {},
 
		fbUpdateRequest : function(force) {
			return "FU;"+(force ? '1' : '0')+";EOF";
		},
		
		pointerUpdateRequest : function(x,y,btns) {
			var btnMask = 0x00;
			if(btns.lClick)
				btnMask |= 0x01;
			if(btns.mClick)
				btnMask |= 0x02;
			if(btns.rClick)
				btnMask |= 0x04;
			if(btns.scrollUp)
				btnMask |= 0x08;
			if(btns.scrollDown)
				btnMask |= 0x16;
			return "PE;"+x+";"+y+";"+btnMask+";EOF";
		},
		
		keyUpdateRequest: function(key,down) {
			if(down) {
				this.keyDowns["K_"+key] = (new Date()).getTime()/1000;
			}
			return "KE;"+(down ? "1" : "0")+";"+key+";EOF";
		},
		
		keyCleaner: function() {
			for(var i in this.keyDowns) {
				if(!this.keyDowns[i])
					return true; 
				if(((new Date()).getTime()/1000)-this.keyDowns[i] > 5000) {
					this.keyUpdateRequest(i);
					delete(this.keyDowns[i]);
				}
			}
		}
	},
	altPressed: false,
	ctrlPressed: false,
	initialize: function(host,port,ssl,container) {
		this.container = $(container) || $(document.body);
		this.socket = new MojaUtils.Socket();
		this.host = host;
		this.port = port;
		this.ssl = ssl;
		this.fbDimensions = {width:0,height:0}
		this.dom = null;
		this.setupKeyCatcher();
		this.pressedKeys = [];
		var scope = this;
		var kc = function() {
			scope.Messages.keyCleaner.call(scope);
		}
		window.setInterval(kc,2000);
		MojaUtils.Observable.prototype.initialize.call(this);
	},
	
	getStatus : function() {
		return this.socket.status;
	},
	
	connect : function() {
		this.url = "ws"+(this.ssl ? "s" : "")+"://"+this.host;
		
		this.socket.connect(this.url,this.port);
		this.registerEvents();
		this.fireEvent("Connecting", [this.host,this.port]);
	},
	
	disconnect: function() {
		this.socket.send("KillMe;EOF");	
		this.socket.__Socket.close();
	},
	
	registerEvents: function() {
		this.socket.registerEvent("onopen",this.openFn,this,true);
		this.socket.registerEvent("onclose",this.closeFn,this,true);
		this.socket.registerEvent("onerror",this.errorFn,this,true);
		this.socket.registerEvent("mouseEv",this.onMouseEv,this,true);
		Event.observe(this.container,"mousemove",this.sendPointerUpdate.bind(this));
		Event.observe(this.container,"mousedown",this.sendPointerUpdate.bind(this));
		Event.observe(this.container,"mouseup",this.sendPointerUpdate.bind(this));
		Event.observe(this.container,"mousedown",this.sendPointerUpdate.bind(this));
		Event.observe(this.container,"mousedown",this.sendPointerUpdate.bind(this));

		document.body.oncontextmenu = function() {return false;};
		this.socket.recvForever({fn:this.handleResponse,scope:this});
		document.body.onkeydown = this.keyDownUpdate.bind(this);
		document.body.onkeyup = this.keyUpUpdate.bind(this);
		/*if($('canvasFlag').checked) {
			MojaUtils.Logger.log("Using canvas");
			this.canvasRenderer = new VNCCanvasRenderer(this);
		} else {
			this.canvasRenderer = null;
		}	*/
		this.renderer = new VNCBase64Renderer(this.container,this,this.canvasRenderer);
	},
	forceCounter : 0,
	setupKeyCatcher: function() {
		var dom = $(document.body);
		if(this.txtArea)
			this.txtArea.parentNode.removeChild(this.txtArea);
		this.txtArea = new Element("input",{
			type:'text',
			style: 'position:absolute;left:-400px;',
			autocorrect : "off",
			autocapitalize : "off"
		});
		dom.insert(this.txtArea);
		this.txtArea.focus();
		
	},
	
	
	openFn: function() {
		this.fireEvent("Connecting", [this.host,this.port]);

	},
	closeFn: function() {
		this.fireEvent("Closed", [this.host,this.port]);
		if(this.pe) 
			this.pe.stop();
	},
	errorFn: function() {
		this.fireEvent("Error", [this.host,this.port]);
		if(this.pe) 
			this.pe.stop();
	},
	
	onDimensionUpdate : function(data) {
		this.fbDimensions.width = data.width;
		this.fbDimensions.height = data.height;
		this.renderer.dimensions = {w:data.width, h:data.height}
		this.dom = new Element("div", {			
			style: "border:1px solid black;position:relative;"+
					"height: "+this.fbDimensions.height+"px"+
					";width: "+this.fbDimensions.width+"px"
		});
		this.container.update();
		this.container.insert(this.dom);
		this.targetDOM = this.dom;
		this.fireEvent("dimensionsChanged",this.fbDimensions);
		if(!this.pe) {
			var scope = this;	
			this.pe = new PeriodicalExecuter(function(pe) {
				if(scope.targetDOM.childNodes.length > 400 && !scope.cleaning) { 
					scope.requestFBUpdate(false);
					scope.cleaning = true;
					window.setTimeout(function() {scope.cleaning = false;},1000);
				}
				scope.requestFBUpdate(true);
			},0.15);
		
		}

	},
	
	handleResponse : function(msg) {
		if(msg == "PING") {
			this.socket.send("PING");
			return true;
		}
        console.log(msg);
		var data = msg.evalJSON();
		if(!data[0])
			return false;
		switch(data[0].type) {
			case 'UD':
				this.onDimensionUpdate(data[0]);
				break;
			case 'PW_REQ':
				this.socket.send("PW;"+window.prompt("Password required")+";EOF");
				break;
			case 'FU':
				this.curRequests--;
				this.renderer.recvUpdate(data[0]);
				break;
			case 'CR':
				this.renderer.copyRect(data[0]);
				break;
		}
		//this.requestFBUpdate();
	},
	
	sendPointerUpdate:function(ev) {
		var x = ev.pageX-this.container.viewportOffset()[0]-this.container.cumulativeScrollOffset()[0];
		var y =	ev.pageY-this.container.viewportOffset()[1]-this.container.cumulativeScrollOffset()[1];;
		this.txtArea.style.top = (ev.pageY-25)+"px";
		//this.txtArea.style.left = y+"px";
	
			
		var btn = this.btns || {
			lClick : false,
			mClick: false,
			rClick : false
		};
		if(ev.type == 'mouseup' || ev.type == 'mousedown')
			switch(ev.which) {
				case 1 :
					btn.lClick = ev.type == 'mousedown';
					break;
				case 2:
					btn.mClick = ev.type == 'mousedown';
					break;
				case 3:				
					btn.rClick = ev.type == 'mousedown';
					break;
				// TODO::
				//scrollUp: type == 'scrollUp',
				//scrollDown: type == 'scrollDown'
			};
			
		this.socket.send(this.Messages.pointerUpdateRequest(x,y,btn));
		this.btns = btn;	
		ev.stop();
		this.txtArea.focus();
	}, 
	
	keyDownUpdate :function(key) {
		
		function chkKey() {
			var special = this.getSpecialCode(key);
			if(special != null) {
				this.socket.send(this.Messages.keyUpdateRequest(special,true));
				key.stop();
				return true;
			}
			
			if(this.txtArea.value.length == 0 && key.ctrlKey) {
				this.txtArea.value = String.fromCharCode(key.keyCode).toLowerCase();
			} else {
				while(this.txtArea.value.length > 1) {
				
					var _char = MojaUtils.String.unicodeAt(this.txtArea.value.substr(0,1));
					if(this.lastDown.length) {
						if(this.lastDown[this.lastDown.length-1] == key.keyCode) {
							
							this.socket.send(this.Messages.keyUpdateRequest(_char));
						}
					}
					this.socket.send(this.Messages.keyUpdateRequest(_char,true));	
					this.txtArea.value = this.txtArea.value.substr(1);
					
			
					if(this.lastDown[this.lastDown.length-1] != key.keyCode)
						this.lastDown.push(key.keyCode);			
				}
			}
			return !key.ctrlKey && !key.altKey;
		}
		var scope = this;
		var checkDelegate = function() {
			chkKey.call(scope,key);
		}
		
		window.setTimeout(checkDelegate,100);
	},
	
	keyUpUpdate :function(key) {
		
		try {
			var special = this.getSpecialCode(key);
			
			if(special != null) {
				this.socket.send(this.Messages.keyUpdateRequest(special,false));
				key.stop();
				return true;
			}
			if(this.txtArea.value.length == 0 && key.ctrlKey)
				this.txtArea.value = String.fromCharCode(key.keyCode).toLowerCase();
			var _char = MojaUtils.String.unicodeAt(this.txtArea.value,0)

			this.txtArea.value = this.txtArea.value.substr(1);
			
			if(isNaN(_char))
				return false;
			this.socket.send(this.Messages.keyUpdateRequest(_char, true));
			var scope = this;
			(function(){
				scope.socket.send(scope.Messages.keyUpdateRequest(_char, false));
			}).delay(0.003);
			if (key.ctrlKey) {
				key.stop();	
				return false;		
			}
			for(var i=0;i<this.lastDown.length;i++) {
				if(this.lastDown[i] == key) {
					this.lastDown.slice(i,1);
					break;
				}
			}
		} catch(e) {
			
		}
	},
	
	requestFBUpdate: function(force) {
		if(this.curRequests > this.maxRequestRate) {
			return false;
		}
		try {
			this.curRequests++;	
			this.socket.send(this.Messages.fbUpdateRequest(force));			
		} catch(e) {
			MojaUtils.Logger.log(e)
			this.curRequests--;
		}
		
	},
	
	getSpecialCode: function(key) {
		var keyMap = {
			'8' :   0xff08,
			'9' :   0xff09,
			'13' :  0xff0d,
			'16' :  0xffe1,
			'17' :	0xffe3,
			//'18' :  0xffe9, // ALT
			'27' :  0xff1b,
			'33' :	0xff55,
			'34' :	0xff56,
			'35' :  0xff57,
			'36' :  0xff50,
			'37' :  0xff51,
			'38' :  0xff52,
			'39' :	0xff53,
			'40' :	0xff54,
			'45' :	0xff63,
			'46' :  0xffff
		};
		var pressedKey = keyMap[key.keyCode];
		
		if(((!this.altPressed && key.altKey)) && !pressedKey)
			pressedKey = key.keyCode;

		this.ctrlPressed = key.ctrlKey;
		this.altPressed = key.altKey;

		return pressedKey;		
	}
});
